﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ControleRepresentante.Logic.Models;
using ControleRepresentante.Logic.Repositories.SearchExtensions;
using ControleRepresentante.Logic.Statistics;
using NHibernate;
using NHibernate.Criterion;
using Attribute = ControleRepresentante.Logic.Models.Attribute;

namespace ControleRepresentante.Logic.Repositories
{
    public interface IClienteRepository : IUsuariosHolderRepository<Cliente, StatisticsBase>
    {
        int GetCount();
        IEnumerable<Cliente> GetModels(string nome, string cidade, string bairro, IEnumerable<Guid> vendedorIds, DateTime? clienteDesdeDe, DateTime? clienteDesdeAte, DateTime? noPedidosDe, DateTime? noPedidosAte, Dictionary<Attribute, string> attributeMap, ISearchExtension[] searchExtensions, out StatisticsBase statistics, int page = -1);
        int[] FilterSynchronizedClientes(int[] clientes);
    }

    public class ClienteRepository : UsuariosHolderRepository<Cliente, StatisticsBase, ClienteError>, IClienteRepository
    {
        public ClienteRepository(IRepositoryConfiguration repositoryConfiguration, ISession session) 
            : base(repositoryConfiguration, session)
        {
        }

        protected override IQueryOver<Cliente, Cliente> DoGetModelsQueryOver(IQueryOver<Cliente, Cliente> queryOver)
        {
            return queryOver.OrderBy(x => x.RazaoSocial).Asc;
        }

        public override Dictionary<Guid, string> GetModelNames()
        {
            return Session.CreateSQLQuery(@"
                SELECT UsuariosHolderId, RazaoSocial
                FROM Cliente
                INNER JOIN AttributeHolder ON UsuariosHolderId = AttributeHolder.Id
                WHERE RepresentanteId = :RepresentanteId
                ORDER BY RazaoSocial")
                .AddScalar("UsuariosHolderId", NHibernateUtil.Guid)
                .AddScalar("RazaoSocial", NHibernateUtil.String)
                .SetParameter("RepresentanteId", GetRepresentanteId())
                .List()
                .Cast<object[]>()
                .ToDictionary(x => (Guid)x[0], x => (string)x[1]);
        }

        public override Dictionary<Guid, string> GetModelNames(string search)
        {
            return Session.CreateSQLQuery(@"
                SELECT UsuariosHolderId, RazaoSocial
                FROM Cliente
                INNER JOIN AttributeHolder ON UsuariosHolderId = AttributeHolder.Id
                WHERE RepresentanteId = :RepresentanteId AND (NomeFantasia LIKE :Search OR RazaoSocial LIKE :Search)
                ORDER BY RazaoSocial")
                .AddScalar("UsuariosHolderId", NHibernateUtil.Guid)
                .AddScalar("RazaoSocial", NHibernateUtil.String)
                .SetParameter("RepresentanteId", GetRepresentanteId())
                .SetParameter("Search", "%" + search + "%")
                .List()
                .Cast<object[]>()
                .ToDictionary(x => (Guid)x[0], x => (string)x[1]);
        }

        protected override void SetProperties(IEnumerable<Cliente> clientes)
        {
            base.SetProperties(clientes);
            if (clientes.Any())
            {
                Dictionary<Guid, string> vendedores = Session.QueryOver<Vendedor>()
                    .Where(x => x.Id.IsIn(clientes.Where(y => y.Vendedor != null).Select(y => y.Vendedor.Id).Distinct().Cast<object>().ToArray()))
                    .List()
                    .ToDictionary(x => x.Id, x => x.Nome);


                Dictionary<Guid, DateTime> lastPedidos = Session.CreateSQLQuery(String.Format(@"
                    SELECT ClienteId, MAX(Data) Data
                    FROM Pedido
                    WHERE ClienteId IN ('{0}')
                    GROUP BY ClienteId", String.Join("','", clientes.Select(x => x.Id))))
                    .AddScalar("ClienteId", NHibernateUtil.Guid)
                    .AddScalar("Data", NHibernateUtil.DateTime)
                    .List()
                    .Cast<object[]>()
                    .ToDictionary(x => (Guid)x[0], x => (DateTime)x[1]);

                foreach (Cliente cliente in clientes)
                {
                    cliente.VendedorNome = cliente.Vendedor == null ? String.Empty : vendedores[cliente.Vendedor.Id];
                    DateTime lastPedido;
                    if (lastPedidos.TryGetValue(cliente.Id, out lastPedido))
                    {
                        cliente.LastPedido = lastPedido;
                    }
                }
            }
        }

        protected override StatisticsBase GetStatistics(int modelCount)
        {
            return new StatisticsBase(modelCount);
        }

        protected override IEnumerable<Expression<Func<Cliente, bool>>> DoGetUniquenessExpressions(Cliente model)
        {
            yield return x => x.RazaoSocial == model.RazaoSocial;
        }

        protected override ClienteError RepresentanteMissmatchError
        {
            get { return ClienteError.RepresentanteMissmatchError; }
        }

        protected override ClienteError DuplicatedError { get { return ClienteError.DuplicatedRazaoSocial; } }

        public int GetCount()
        {
            return Session.QueryOver<Cliente>()
                .Where(x => x.Representante.Id == GetRepresentanteId())
                .RowCount();
        }

        public IEnumerable<Cliente> GetModels(string nome, string cidade, string bairro, IEnumerable<Guid> vendedorIds, DateTime? clienteDesdeDe, DateTime? clienteDesdeAte, DateTime? noPedidosDe, DateTime? noPedidosAte, Dictionary<Attribute, string> attributeMap, ISearchExtension[] searchExtensions, out StatisticsBase statistics, int page = -1)
        {
            const string TableAlias = "cl";

            ICriteria criteria = CreateCriteria(cidade, bairro, attributeMap, TableAlias);

            if (!String.IsNullOrEmpty(nome))
            {
                string likeNome = "%" + nome + "%";
                criteria = criteria.Add(Restrictions.Or(
                    Restrictions.InsensitiveLike(Projections.Property<Cliente>(x => x.RazaoSocial), likeNome),
                    Restrictions.InsensitiveLike(Projections.Property<Cliente>(x => x.NomeFantasia), likeNome)));
            }

            if (vendedorIds != null && vendedorIds.Any())
            {
                criteria = criteria.Add(
                    Restrictions.In(Projections.Property<Cliente>(x => x.Vendedor.Id), vendedorIds.ToArray()));
            }

            IEnumerable<Vendedor> vendedorFiltro = User.Filtros.OfType<Vendedor>();
            if (vendedorFiltro.Any())
            {
                criteria = criteria.Add(
                    Restrictions.In(Projections.Property<Cliente>(x => x.Vendedor.Id), vendedorFiltro.Select(x =>x.Id).ToArray()));
            }

            IEnumerable<Cliente> clienteFiltro = User.Filtros.OfType<Cliente>();
            if (clienteFiltro.Any())
            {
                criteria.Add(Restrictions.In(Projections.Property<Cliente>(x => x.Id), clienteFiltro.Select(y => y.Id).ToArray()));
            }

            if (clienteDesdeDe.HasValue)
            {
                criteria.Add(Restrictions.Ge(Projections.Property<Cliente>(x => x.ClienteDesde), clienteDesdeDe.Value));
            }

            if (clienteDesdeAte.HasValue)
            {
                criteria.Add(Restrictions.Le(Projections.Property<Cliente>(x => x.ClienteDesde), clienteDesdeAte.Value));
            }

            if (noPedidosDe.HasValue || noPedidosAte.HasValue)
            {
                DetachedCriteria pedidoCriteria = DetachedCriteria.For<Pedido>()
                    .SetProjection(Projections.SqlProjection("COUNT(*)", null, null))
                    .Add(Restrictions.EqProperty(TableAlias + ".Id", Projections.Property<Pedido>(x => x.Cliente.Id)));
                if (noPedidosDe.HasValue)
                {
                    pedidoCriteria.Add(Restrictions.Ge(Projections.Property<Pedido>(x => x.Data), noPedidosDe.Value));
                }
                if (noPedidosAte.HasValue)
                {
                    pedidoCriteria.Add(Restrictions.Le(Projections.Property<Pedido>(x => x.Data), noPedidosAte.Value));
                }
                criteria = criteria.Add(Restrictions.Eq(Projections.SubQuery(pedidoCriteria), 0));
            }

            if (searchExtensions != null)
            {
                foreach (ISearchExtension searchExtension in searchExtensions)
                {
                    criteria = searchExtension.Execute(criteria, TableAlias);
                }
            }

            int modelCount;
            IEnumerable<Cliente> clientes = FuturePaging(criteria, page, out modelCount, x => x.AddOrder(Order.Asc(Projections.Property<Cliente>(y => y.RazaoSocial))));
            SetProperties(clientes);
            statistics = GetStatistics(modelCount);
            return clientes;
        }

        public int[] FilterSynchronizedClientes(int[] clientes)
        {
            EnsureIsRepresentante();

            DetachedCriteria detachedCriteria = DetachedCriteria.For<Cliente>()
                .SetProjection(Projections.Property<Cliente>(x => x.Id))
                .Add(Restrictions.EqProperty("cj.Id", Projections.Property<Cliente>(x => x.Id)))
                .Add(Restrictions.Eq(Projections.Property<Cliente>(x => x.Representante.Id), GetRepresentanteId()));
            IEnumerable<int> synchronizedClientes = Session.CreateCriteria<ClienteJolitex>("cj")
                .SetProjection(Projections.Property<ClienteJolitex>(x => x.Codigo))
                .Add(Restrictions.In(Projections.Property<ClienteJolitex>(x => x.Codigo), clientes.ToArray()))
                .Add(Subqueries.Exists(detachedCriteria))
                .Future<int>()
                .Distinct();
            return clientes.Except(synchronizedClientes).ToArray();
        }

        protected override IQueryOver<Cliente, Cliente> GetModelsQueryOver(IQueryOver<Cliente, Cliente> queryOver)
        {
            IQueryOver<Cliente, Cliente> result = base.GetModelsQueryOver(queryOver);
            IEnumerable<Vendedor> vendedorFiltro = User.Filtros.OfType<Vendedor>();
            if (vendedorFiltro.Any())
            {
                result = result.And(Restrictions.In(Projections.Property<Cliente>(x => x.Vendedor.Id), vendedorFiltro.Select(y => y.Id).ToArray()));
            }
            return result;
        }
    }

    public enum ClienteError
    {
        DuplicatedRazaoSocial,
        RepresentanteMissmatchError
    }
}
